/*
 * Copyright 2010 Laurent Moulinier, Christian Matzat and others
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

package com.gafmedia.graph;

import android.content.Context;
import android.graphics.*;
import android.util.AttributeSet;

import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;

/**
 * View that renders a line chart.
 * <p/>
 * based on code from GAFMEDIA Studio, initiated by Laurent Moulinier
 * <a href="http://blog.gafmediastudio.com/2010/04/01/draw-a-line-chart-with-android/"></a>
 */
public class LineChartView extends BaseChartView {

    private static final String TAG = BASE_TAG + LineChartView.class.getSimpleName();

    public final int LABEL_X_SHOW = 7;
    public final int LABEL_Y_SHOW = 6;

    private boolean fillGraph;
    private boolean showAverageLine;
    private boolean showGrid;
    private boolean zoomChart;
    private boolean flipYScale;
    private boolean showXRangeHeadline;

    private int gridColor;
    private int averageLineColor;
    private int textColor;

    private String title;
    private List<LineChartData> lineChartDataList = new ArrayList<LineChartData>();

    private Bitmap cachedBitmap;
    private boolean initialDrawingIsPerformed;

    public LineChartView(Context context) {
        super(context);
    }

    public LineChartView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    // this method draws the chart
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        switch (state) {
            case WAIT:
                return;
            case IS_READY_TO_DRAW:
                this.cachedBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
                Canvas cacheCanvas = new Canvas(this.cachedBitmap);
                initialDraw(cacheCanvas);
                canvas.drawBitmap(this.cachedBitmap, 0, 0, new Paint());
                break;
            default:
                canvas.drawBitmap(this.cachedBitmap, 0, 0, new Paint());
        }
    }

    private void initialDraw(Canvas canvas) {
        Paint backgroundPaint = new Paint();
        backgroundPaint.setStyle(Paint.Style.FILL);
        backgroundPaint.setColor(backgroundColor);
        canvas.drawPaint(backgroundPaint);

        Paint linePaint = new Paint();
        if (fillGraph) {
            linePaint.setStyle(Paint.Style.FILL_AND_STROKE);
        } else {
            linePaint.setStyle(Paint.Style.FILL);
        }
        linePaint.setAntiAlias(true);
        linePaint.setStrokeWidth(1.5f);

        Paint gridPaintDot = new Paint();
        gridPaintDot.setColor(gridColor);
        gridPaintDot.setAntiAlias(false);
        gridPaintDot.setStyle(Paint.Style.STROKE);
        gridPaintDot.setStrokeWidth(0.5f);
        gridPaintDot.setPathEffect(new DashPathEffect(new float[]{5, 5}, 1));

        Paint gridPaintFull = new Paint();
        gridPaintFull.setColor(gridColor);
        gridPaintFull.setAntiAlias(false);
        gridPaintFull.setStyle(Paint.Style.STROKE);
        gridPaintFull.setStrokeWidth(1.0f);

        Paint titlePaint = new Paint();
        titlePaint.setStyle(Paint.Style.FILL);
        titlePaint.setTextAlign(Paint.Align.LEFT);
        titlePaint.setColor(textColor);
        titlePaint.setTextSize(10);

        Rect textRect = new Rect();
        //----------------------------------------------------------------------------------
        // Get Y Max Values
        //----------------------------------------------------------------------------------
        String stringLabel;
        float y;
        int index;
        double value;
        float oldX;
        float oldY;
        float newX;
        float newY;
        double maxY = 0;
        double minY;
        int valuesCount = lineChartDataList.get(0).lineChartItems.size();
        //----------------------------------------------------------------------------------
        // GET Y MIN & MAX
        //----------------------------------------------------------------------------------
        for (LineChartData lineChartData : lineChartDataList) {
            if (maxY < lineChartData.maxY) maxY = lineChartData.maxY;
        }
        if (zoomChart) {
            minY = maxY;
            for (LineChartData lineChartData : lineChartDataList) {
                if (minY > lineChartData.minY) minY = lineChartData.minY;
            }
        } else {
            minY = 0;
        }

        //----------------------------------------------------------------------------------
        // DRAW Y AXIS
        //----------------------------------------------------------------------------------
        float step = (float) (maxY - minY) / (float) (LABEL_Y_SHOW);
        float stepY = (float) (height - gapTop - gapBottom) / (float) (maxY - minY);
        for (int i = 0; i <= LABEL_Y_SHOW; i++) {
            y = ((i * step) * stepY);
            oldX = (gapLeft - 2f);
            oldY = (height - gapBottom - y);
            newX = (float) (width - gapRight);
            newY = (height - gapBottom - y);
            if (i == 0) {
                canvas.drawLine(oldX, oldY, newX, newY, gridPaintFull);
            } else {
                if (showGrid) {
                    canvas.drawLine(oldX, oldY, newX, newY, gridPaintDot);
                }
            }

            if (flipYScale) {
                value = maxY - (i * step);
            } else {
                value = minY + (i * step);
            }
            // TODO implement parameter for decimals
            stringLabel = String.format("%3.2f",value);
            canvas.drawText(stringLabel, 2, newY, titlePaint);
        }
        //----------------------------------------------------------------------------------
        // DRAW X AXIS
        //----------------------------------------------------------------------------------
        float stepX = (float) (width - gapLeft - gapRight) / (float) (LABEL_X_SHOW - 1);
        float IndexStep = (float) valuesCount / (float) LABEL_X_SHOW;
        newY = (float) (height - gapBottom);

        for (int j = 0; j < LABEL_X_SHOW; j++) {
            index = (int) ((float) (j) * (float) IndexStep);
            if (index >= valuesCount) index = valuesCount - 1;
            if (j == LABEL_X_SHOW - 1) index = valuesCount - 1;
            newX = ((float) gapLeft + (j * stepX));

            if (j == 0) {
                canvas.drawLine(newX, gapTop, newX, newY + 3, gridPaintFull);
            } else {
                if (showGrid) {
                    canvas.drawLine(newX, gapTop, newX, newY + 3, gridPaintDot);
                }
            }

            LineChartItem chartItem = lineChartDataList.get(0).lineChartItems.get(index);
            titlePaint.getTextBounds(chartItem.entry, 0, chartItem.entry.length(), textRect);
            newX = newX - textRect.right / 2;
            canvas.drawText(chartItem.entry, newX, (height - gapBottom) + 13, titlePaint);
        }
        //--------------------------------------------------------------------------
        // Draw Graph Lines
        //--------------------------------------------------------------------------
        for (LineChartData chartData : lineChartDataList) {

            float pointStepX = (float) (width - gapLeft - gapRight) / (valuesCount - 1f);
            float pointStepY = (float) (height - gapTop - gapBottom) / (float) (maxY - minY);
            float drawOffsetX = (float) gapLeft;
            float drawOffsetY = (float) (height - gapBottom);
            //--------------------------------------------------------------------------
            LineChartItem chartItem = chartData.lineChartItems.get(0);
            oldX = drawOffsetX;
            if (flipYScale) {
                oldY = drawOffsetY - pointStepY * (float) (maxY - chartItem.value);
            } else {
                oldY = drawOffsetY - pointStepY * (float) (chartItem.value - minY);
            }
            Path path = new Path();
            for (int k = 0; k < valuesCount; k++) {
                chartItem = chartData.lineChartItems.get(k);
                if (flipYScale) {
                    newY = drawOffsetY - (pointStepY * (float) (maxY - chartItem.value));
                } else {
                    newY = drawOffsetY - (pointStepY * (float) (chartItem.value - minY));
                }
                newX = drawOffsetX + (pointStepX * (float) k);
                // set default linecolor
                linePaint.setColor(chartData.lineColor);
                if (chartItem.color != 0) {
                    // set special linecolor if available
                    linePaint.setColor(chartItem.color);
                }
                if (!fillGraph) {
                    canvas.drawLine(oldX, oldY, newX, newY, linePaint);
                } else {
                    path.rewind();
                    path.moveTo(oldX, oldY);
                    path.lineTo(newX, newY);
                    path.lineTo(newX, drawOffsetY);
                    path.lineTo(oldX, drawOffsetY);
                    path.close();
                    canvas.drawPath(path, linePaint);
                }
                oldX = newX;
                oldY = newY;
            }
            //--------------------------------------------------------------------------
            // Average Line
            //--------------------------------------------------------------------------
            if (showAverageLine && chartData.hasAverageLine) {
                Paint averagePaint = new Paint();
                averagePaint.setColor(averageLineColor);
                averagePaint.setStyle(Paint.Style.FILL);
                averagePaint.setAntiAlias(true);
                averagePaint.setStrokeWidth(1.5f);
                averagePaint.setPathEffect(new DashPathEffect(new float[]{5, 5}, 1));

                Paint averageTxtPaint = new Paint();
                averageTxtPaint.setStyle(Paint.Style.FILL);
                averageTxtPaint.setTextAlign(Paint.Align.LEFT);
                averageTxtPaint.setColor(averageLineColor);
                averageTxtPaint.setTextSize(10);

                DecimalFormat FloatFormatter = new DecimalFormat("0.##");
                String StrAverage = "" + FloatFormatter.format(chartData.graphAverage);
                averageTxtPaint.getTextBounds(StrAverage, 0, StrAverage.length(), textRect);
                newX = (float) (width - gapRight - textRect.right) - 3.0f;
                newY = (float) (gapTop - textRect.top) + 3.0f;
                canvas.drawText(StrAverage, newX, newY, averageTxtPaint);
                if (flipYScale) {
                    newY = (float) (drawOffsetY - (pointStepY * (maxY - chartData.graphAverage)));
                } else {
                    newY = (float) (drawOffsetY - (pointStepY * (chartData.graphAverage - minY)));
                }
                canvas.drawLine(gapLeft, newY, width - gapRight, newY, averagePaint);
            }
        }
        //--------------------------------------------------------------------------
        // title
        //--------------------------------------------------------------------------
        if (showXRangeHeadline) {
            String xRangeTitle = lineChartDataList.get(0).lineChartItems.get(0).entry + " ... "
                    + lineChartDataList.get(0).lineChartItems.get(valuesCount - 1).entry;
            titlePaint.getTextBounds(xRangeTitle, 0, xRangeTitle.length(), textRect);
            canvas.drawText(xRangeTitle, (width - textRect.right) / 2, 10, titlePaint);
        }

        if (title != null) {
            titlePaint.getTextBounds(title, 0, title.length(), textRect);
            canvas.drawText(title, (width - textRect.right) / 2, showXRangeHeadline ? 20 : 10, titlePaint);
        }
        state = IS_DRAWN;
    }

    /**
     * Sets the geometry values of the chart.
     * @param width the width
     * @param height the height
     * @param gapLeft gap from left
     * @param gapRight gap from right
     * @param gapTop gap from top
     * @param gapBottom gap from bottom
     */
    public void setGeometry(int width, int height, int gapLeft, int gapRight, int gapTop, int gapBottom) {
        this.width = width;
        this.height = height;
        this.gapLeft = gapLeft;
        this.gapRight = gapRight;
        this.gapTop = gapTop;
        this.gapBottom = gapBottom;
    }

    /**
     * Sets the paramters that define how the line chart should be displayed
     *
     * @param title title of the whole chart
     * @param backgroundColor color of the background
     * @param gridColor color of the grid
     * @param textColor color of the text (legend)
     * @param fillGraph switches if the area below the line graph should be filled
     * @param showAverageLine switches if an average line should be displayed
     * @param showGrid switches if a grid should be drawn in the chart area
     * @param zoomChart switches if the chart should be zoomed in. The chart area starts then from the min value and
     *                    ends at the max value. Otherwise the chart will always start at zero.
     * @param flipYScale switches if the Y-scale should be flipped. Otherwise the chart starts with min value at the
     *                    bottom line up to the max value (see also parameter zoomChart)
     * @param showXRangeHeadline switches if a headline generated from the x-range legend values should be displayed
     */
    public void setSkinParams(String title, int backgroundColor, int gridColor, int textColor, int averageLineColor,
                              boolean fillGraph, boolean showAverageLine, boolean showGrid, boolean zoomChart,
                              boolean flipYScale, boolean showXRangeHeadline) {
        this.title = title;
        this.backgroundColor = backgroundColor;
        this.gridColor = gridColor;
        this.textColor = textColor;
        this.averageLineColor = averageLineColor;
        this.fillGraph = fillGraph;
        this.showAverageLine = showAverageLine;
        this.showGrid = showGrid;
        this.zoomChart = zoomChart;
        this.flipYScale = flipYScale;
        this.showXRangeHeadline = showXRangeHeadline;
    }

    /**
     * Add a new list of LineChartItems.
     *
     * @param lineChartItems list of LineChartItems
     * @param lineColor      Color of the graph line
     * @param hasAverageLine The graph average line is computed out of this graph line. The last added line with this value
     *                       set to <b>true</b> shows the average line.
     */
    public void addData(List<LineChartItem> lineChartItems, int lineColor, boolean hasAverageLine) {

        if (lineChartDataList.size() > 0) {
            if (lineChartDataList.get(0).lineChartItems.size() != lineChartItems.size()) {
                throw new IllegalArgumentException("Count of ChartItemValue is different.");
            }
            for (LineChartData lineChartData : lineChartDataList) {
                if (hasAverageLine && lineChartData.hasAverageLine) {
                    lineChartData.hasAverageLine = false;
                }
            }
        }
        lineChartDataList.add(new LineChartData(lineChartItems, lineColor, hasAverageLine));

        state = IS_READY_TO_DRAW;
    }

    // Inner class that holds the graphs values
    private class LineChartData {

        int lineColor;
        List<LineChartItem> lineChartItems;
        boolean hasAverageLine;
        double maxY, minY;
        float graphAverage;

        LineChartData(List<LineChartItem> LineChartValues, int lineColor, boolean hasAverageLine) {
            this.lineColor = lineColor;
            this.lineChartItems = LineChartValues;
            this.hasAverageLine = hasAverageLine;
            computeMinMaxAverage();
        }

        // Computes min and max values for each graph
        private void computeMinMaxAverage() {
            for (LineChartItem chartItem : lineChartItems) {
                if (maxY < chartItem.value) maxY = chartItem.value;
                graphAverage += chartItem.value;
            }
            if (zoomChart) {
                minY = maxY;
                for (LineChartItem chartItem : lineChartItems) {
                    if (minY > chartItem.value) minY = chartItem.value;
                }
            } else {
                minY = 0;
            }
            if (hasAverageLine) {
                graphAverage = graphAverage / lineChartItems.size();
            }
        }
    }
}

